/* 该类实现对文件进行操作 */

#ifndef __MY_FILEUTIL__
#define __MY_FILEUTIL__
#include <iostream>
#include <string>
#include <fstream>
#include <vector>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>
#include <experimental/filesystem>      // c++17 的文件系统库
#include <bundle.h>
#include "DataBaseHelper.hpp"
#include "log.hpp"

extern Log my_log;  // 在cloud.cpp中定义的日志类

namespace cloud
{
namespace fs = std::experimental::filesystem;

class FileUtil
{
private:
    std::string _fileName;
    struct stat _st;        // 用于获取文件的各种属性
public:
    // 输入一个路径
    FileUtil(std::string fileName) : _fileName(fileName) {
        if(stat(_fileName.c_str(), &_st) < 0) {
            // std::cerr << "warning: 获取文件属性失败!\nstat(_fileName.c_str(), &_st) error! why: " << strerror(errno) << '\n';
            return;
        }
    }

    // 获取文件大小(单位是字节)
    int64_t getFileSize()
    {
        return _st.st_size;      // 返回字节数，有符号的长整型
    }

    // 获取文件最后一次修改时间
    time_t getLastMTime()
    {
        return _st.st_mtime;
    }

    // 获取文件最后一次访问时间
    time_t getLastATime()
    {
        return _st.st_atime;
    }

    // 获得一个路径最后文件的名称，例如/abc/a.txt -> a.txt
    std::string getFileName()
    {
        ssize_t pos = _fileName.find_last_of('/');

        if(pos == std::string::npos) {
            // 没有/，证明这就是一个文件，没有文件夹
            return _fileName;
        }

        // 有/，要截取字符串
        return _fileName.substr(pos+1);
    }

    // 从pos位置获取len长度的数据(单位是字节)，写到s中
    bool getContentFromPos(std::string *s, size_t pos, size_t len)
    {
        int64_t fileSz = getFileSize();

        if(pos + len > fileSz) {
            // 申请的长度大于文件长度
            my_log(ERROR, "Bad size! in bool getContentFromPos(std::string *s, size_t pos, size_t len)\n");
            // std::cerr << "Bad size!\nbool getContentFromPos(std::string *s, size_t pos, size_t len)\n";
            return false;
        }

        // 用来读取文件
        std::ifstream ifile(_fileName, std::ios::binary);

        // 是否打开成功
        if(ifile.is_open() == false) {
            my_log(ERROR, "Open file error! in bool getContentFromPos(std::string *s, size_t pos, size_t len)\n");
            // std::cerr << "Open file error!\nbool getContentFromPos(std::string *s, size_t pos, size_t len)\n";
            ifile.close();
            return false;
        }
        
        // 读取文件内容
        ifile.seekg(pos, std::ios::beg);
        s->resize(len);     
        ifile.read((char*)(s->c_str()), len);

        // 检测这个流的状态是否ok
        if(ifile.good() == false) {
            my_log(ERROR, "read file error! in bool getContentFromPos(std::string *s, size_t pos, size_t len)\n");
            // std::cerr << "read file error!\nbool getContentFromPos(std::string *s, size_t pos, size_t len)\n";
            ifile.close();
            return false;
        }

        return true;
    }

    // 获取文件的所有内容，写到s中
    bool getFullContent(std::string *s) 
    {
        // 调用getContentFromPos()
        return getContentFromPos(s, 0, getFileSize());
    }

    // 将字符串s的内容写入到文件中
    bool setContent(const std::string& s)
    {
        // 用来像文件写入
        std::ofstream ofile(_fileName, std::ios::binary);

        // 是否打开成功
        if(ofile.is_open() == false) {
            my_log(ERROR, "Open file error! in bool setContent(const std::string& s)\n");
            // std::cerr << "Open file error!\nbool setContent(const std::string& s)\n";
            ofile.close();
            return false;
        }

        ofile.write((char*)s.c_str(), s.size());

        // 检测这个流的状态是否ok
        if(ofile.good() == false) {
            my_log(ERROR, "write file error! in bool setContent(const std::string& s)\n");
            // std::cerr << "write file error!\nbool setContent(const std::string& s)\n";
            ofile.close();
            return false;
        }

        return true;
    }

    // 压缩，packName是压缩包的名字
    bool compresss(const std::string& packName)
    {
        // 先读取文件内容
        std::string content;
        
        if(getFullContent(&content) == false) {
            my_log(ERROR, "bool compresss(const std::string& packName) get content error! in bool compresss(const std::string& packName)\n");
            // std::cerr << "bool compresss(const std::string& packName) get content error!\n";
            return false;
        }

        // 压缩文件内容，这里使用LZIP压缩格式
        std::string packContent = bundle::pack(bundle::LZIP, content);
        cloud::FileUtil newFile(packName);

        // 将内容写到新的文件
        if(newFile.setContent(packContent) == false) {
            my_log(ERROR, "bool compresss(const std::string& packName) set content error! in bool compresss(const std::string& packName)\n");
            // std::cerr << "bool compresss(const std::string& packName) set content error!\n";
            return false;
        }

        return true;
    }

    // 解压，fileName是解压后新文件的名字
    bool unCompress(const std::string& fileName)
    {
        // 先读取文件内容
        std::string content;
        
        if(getFullContent(&content) == false) {
            my_log(ERROR, "bool unCompress(const std::string& packName) get content error!\n");
            // std::cerr << "bool unCompress(const std::string& packName) get content error!\n";
            return false;
        }

        // 解压文件内容
        std::string unpackContent = bundle::unpack(content);
        cloud::FileUtil newFile(fileName);

        // 将内容写到新的文件
        if(newFile.setContent(unpackContent) == false) {
            my_log(ERROR, "bool unCompresss(const std::string& packName) set content error!\n");
            // std::cerr << "bool unCompresss(const std::string& packName) set content error!\n";
            return false;
        }

        return true;
    }

    // 判断文件是否存在，存在返回true
    bool exits()
    {
        return fs::exists(_fileName);
    }

    // 创建目录，创建成功返回true
    bool createDir()
    {
        // 如果该文件存在了，就直接返回true
        if(exits())    return true;
        return fs::create_directories(_fileName);
    }

    // 扫描文件夹下的文件，放到数组中
    bool scanDir(std::vector<std::string> *array)
    {   
        for (const fs::directory_entry& entry : fs::directory_iterator(_fileName)) {
            if(fs::is_directory(entry) == true) {
                // 如果是目录的话，跳过，该函数只扫描一般文件
                continue;
            }

            array->push_back(entry.path().relative_path().string());
        }   
        return true;
    }

    // 删除文件
    bool removeFile()
    {
        if(exits() == false) {
            // 文件本身就不存在, 删除就没有意义了
            return true;
        }

        if(remove(_fileName.c_str()) < 0) {
            std::cerr << "删除文件失败!\n why: " << strerror(errno) << '\n';
            return false;
        }
        return true;
    }

    // 获得文件的uuid，用于主键，这里使用文件文件名+inode号
    std::string getUuid()
    {
        std::string uuid = getFileName() + "+" + std::to_string(_st.st_ino);
        return uuid;
    }
};
}
#endif